//! Implementation of the ls command

use crate::commands::BuiltinCommand;
use crate::error::{Result, ShellError};
use crate::shell::Shell;
use std::fs;
use std::path::Path;
use std::time::SystemTime;

/// Built-in ls command implementation
pub struct LsCommand;

impl LsCommand {
    /// Create a new ls command
    pub fn new() -> Self {
        Self
    }
    
    /// Format file size in human-readable format
    fn format_size(size: u64) -> String {
        const UNITS: &[&str] = &["B", "K", "M", "G", "T"];
        let mut size = size as f64;
        let mut unit_index = 0;
        
        while size >= 1024.0 && unit_index < UNITS.len() - 1 {
            size /= 1024.0;
            unit_index += 1;
        }
        
        if unit_index == 0 {
            format!("{:>7}", size as u64)
        } else {
            format!("{:>6.1}{}", size, UNITS[unit_index])
        }
    }
    
    /// Format file permissions (simplified for cross-platform)
    fn format_permissions(metadata: &fs::Metadata) -> String {
        let mut perms = String::with_capacity(10);
        
        // File type
        if metadata.is_dir() {
            perms.push('d');
        } else if metadata.is_symlink() {
            perms.push('l');
        } else {
            perms.push('-');
        }
        
        // Owner permissions
        perms.push_str("rwx"); // Simplified - always show as readable/writable/executable
        
        // Group permissions
        perms.push_str("r-x"); // Simplified
        
        // Other permissions
        if metadata.permissions().readonly() {
            perms.push_str("r--");
        } else {
            perms.push_str("r-x");
        }
        
        perms
    }
    
    /// Format modification time
    fn format_time(time: SystemTime) -> String {
        match time.duration_since(SystemTime::UNIX_EPOCH) {
            Ok(duration) => {
                let secs = duration.as_secs();
                let datetime = chrono::DateTime::from_timestamp(secs as i64, 0)
                    .unwrap_or_else(|| chrono::Utc::now());
                datetime.format("%b %d %H:%M").to_string()
            }
            Err(_) => "Unknown".to_string(),
        }
    }
    
    /// List directory contents with detailed information
    async fn list_detailed(&self, path: &Path, show_hidden: bool) -> Result<String> {
        let entries = fs::read_dir(path)
            .map_err(|e| ShellError::Io(e))?;
        
        let mut output = String::new();
        let mut items = Vec::new();
        
        // Collect all entries
        for entry in entries {
            let entry = entry.map_err(|e| ShellError::Io(e))?;
            let file_name = entry.file_name().to_string_lossy().to_string();
            
            // Skip hidden files unless requested
            if !show_hidden && file_name.starts_with('.') {
                continue;
            }
            
            let metadata = entry.metadata().map_err(|e| ShellError::Io(e))?;
            let modified = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
            
            items.push((file_name, metadata, modified));
        }
        
        // Sort by name
        items.sort_by(|a, b| a.0.cmp(&b.0));
        
        // Format output
        for (name, metadata, modified) in items {
            let perms = Self::format_permissions(&metadata);
            let size = if metadata.is_dir() {
                "    <DIR>".to_string()
            } else {
                Self::format_size(metadata.len())
            };
            let time = Self::format_time(modified);
            
            output.push_str(&format!("{} {} {} {}\n", perms, size, time, name));
        }
        
        Ok(output)
    }
    
    /// List directory contents in simple format
    async fn list_simple(&self, path: &Path, show_hidden: bool) -> Result<String> {
        let entries = fs::read_dir(path)
            .map_err(|e| ShellError::Io(e))?;
        
        let mut items = Vec::new();
        
        for entry in entries {
            let entry = entry.map_err(|e| ShellError::Io(e))?;
            let file_name = entry.file_name().to_string_lossy().to_string();
            
            // Skip hidden files unless requested
            if !show_hidden && file_name.starts_with('.') {
                continue;
            }
            
            let metadata = entry.metadata().map_err(|e| ShellError::Io(e))?;
            
            // Add directory indicator
            if metadata.is_dir() {
                items.push(format!("{}/", file_name));
            } else {
                items.push(file_name);
            }
        }
        
        // Sort by name
        items.sort();
        
        // Format in columns (simplified - just one per line for now)
        Ok(items.join("\n") + "\n")
    }
}

#[async_trait::async_trait]
impl BuiltinCommand for LsCommand {
    async fn execute(&self, shell: &Shell, args: Vec<String>) -> Result<String> {
        let mut show_hidden = false;
        let mut long_format = false;
        let mut target_path = shell.current_dir().to_path_buf();
        
        // Parse arguments
        let mut i = 0;
        while i < args.len() {
            let arg = &args[i];
            
            if arg.starts_with('-') {
                for ch in arg.chars().skip(1) {
                    match ch {
                        'a' => show_hidden = true,
                        'l' => long_format = true,
                        'h' => {
                            return Ok(self.usage().to_string());
                        }
                        _ => {
                            return Err(ShellError::invalid_command(
                                format!("Unknown option: -{}", ch)
                            ));
                        }
                    }
                }
            } else {
                // Path argument
                target_path = Path::new(arg).to_path_buf();
                if target_path.is_relative() {
                    target_path = shell.current_dir().join(&target_path);
                }
            }
            
            i += 1;
        }
        
        // Check if path exists
        if !target_path.exists() {
            return Err(ShellError::invalid_command(
                format!("Path does not exist: {}", target_path.display())
            ));
        }
        
        if !target_path.is_dir() {
            return Err(ShellError::invalid_command(
                format!("Path is not a directory: {}", target_path.display())
            ));
        }
        
        // Execute listing
        if long_format {
            self.list_detailed(&target_path, show_hidden).await
        } else {
            self.list_simple(&target_path, show_hidden).await
        }
    }
    
    fn name(&self) -> &'static str {
        "ls"
    }
    
    fn description(&self) -> &'static str {
        "List directory contents"
    }
    
    fn usage(&self) -> &'static str {
        "Usage: ls [OPTIONS] [PATH]\n\nOptions:\n  -a    Show hidden files\n  -l    Use long listing format\n  -h    Show this help message\n\nExamples:\n  ls          List current directory\n  ls -la      List all files in long format\n  ls /path    List specific directory"
    }
}

impl Default for LsCommand {
    fn default() -> Self {
        Self::new()
    }
}